5.14. Справочник по Swift
Справочник по Swift
Основы языка
1. Общие принципы языка Swift
Swift — это современный, типобезопасный, компилируемый язык программирования, разработанный Apple для создания приложений на платформах iOS, macOS, watchOS, tvOS и других. Он сочетает в себе высокую производительность, читаемость кода и мощные возможности абстракции.
Swift поддерживает:
- Статическую типизацию
- Вывод типов
- Управление памятью через ARC (Automatic Reference Counting)
- Функциональное, объектно-ориентированное и протокол-ориентированное программирование
- Безопасность выполнения за счёт проверок на этапе компиляции
2. Синтаксис и структура программы
Программа на Swift состоит из последовательности объявлений, выражений и операторов. Точка с запятой не обязательна в конце строки, но может использоваться для разделения нескольких выражений в одной строке.
print("Hello, world!")
Это минимальная программа на Swift, выводящая текст в консоль.
3. Переменные и константы
3.1. Константы (let)
Константа — это значение, которое устанавливается один раз и не изменяется в дальнейшем.
let pi = 3.14159
let name: String = "Alice"
Тип константы может быть указан явно или выведен автоматически.
3.2. Переменные (var)
Переменная — это значение, которое может изменяться после инициализации.
var counter = 0
counter = 1
var message: String
message = "Ready"
4. Типы данных
Swift предоставляет богатую систему встроенных типов.
4.1. Целочисленные типы
Int— целое число со знаком, размер зависит от архитектуры (32 или 64 бита)Int8,Int16,Int32,Int64— целые числа фиксированного размераUInt,UInt8,UInt16,UInt32,UInt64— целые числа без знака
Примеры:
let age: Int = 25
let byte: UInt8 = 255
4.2. Числа с плавающей точкой
Float— 32-битное число с плавающей точкойDouble— 64-битное число с плавающей точкой (используется по умолчанию)
let height: Float = 175.5
let distance = 123.456 // тип Double
4.3. Логический тип
Bool— принимает значенияtrueилиfalse
let isActive = true
let isComplete: Bool = false
4.4. Строки и символы
String— последовательность символов UnicodeCharacter— одиночный символ
let greeting = "Hello"
let exclamation: Character = "!"
Строки поддерживают интерполяцию:
let name = "Bob"
let message = "Hello, \(name)!"
4.5. Кортежи (Tuples)
Кортеж — это составной тип, группирующий несколько значений.
let httpStatus = (200, "OK")
let person = (name: "Eve", age: 30)
print(person.name) // "Eve"
Кортежи могут быть именованными или безымянными.
4.6. Опционалы (Optional)
Опционал — это тип, который может содержать значение или быть nil.
var optionalName: String? = "Tim"
optionalName = nil
Распаковка опционалов:
- Принудительная:
value! - Опциональная привязка:
if let value = optional { ... } - Nil-coalescing:
value ?? defaultValue
5. Операторы
5.1. Арифметические
+— сложение-— вычитание*— умножение/— деление%— остаток от деления
5.2. Сравнения
==,!=— равенство и неравенство<,<=,>,>=— сравнение порядка
5.3. Логические
&&— логическое И||— логическое ИЛИ!— логическое НЕ
5.4. Присваивания
=— простое присваивание+=,-=,*=,/=,%=— составные присваивания
5.5. Тернарный условный оператор
let result = condition ? valueIfTrue : valueIfFalse
5.6. Диапазоны
- Замкнутый:
a...b— включаетb - Полуоткрытый:
a..<b— не включаетb
for i in 1...5 { print(i) } // 1, 2, 3, 4, 5
for j in 0..<3 { print(j) } // 0, 1, 2
6. Коллекции
6.1. Массивы (Array)
Упорядоченный список значений одного типа.
var numbers = [1, 2, 3]
numbers.append(4)
let first = numbers[0]
Инициализация:
let emptyArray: [Int] = []
var strings = Array<String>()
6.2. Словари (Dictionary)
Коллекция пар «ключ-значение».
var capitals = ["France": "Paris", "Japan": "Tokyo"]
capitals["Germany"] = "Berlin"
let franceCapital = capitals["France"] // Optional<String>
Инициализация:
let emptyDict: [String: Int] = [:]
var scores = Dictionary<String, Int>()
6.3. Множества (Set)
Неупорядоченная коллекция уникальных значений.
var genres: Set<String> = ["Rock", "Jazz", "Pop"]
genres.insert("Blues")
Требования: элементы должны соответствовать протоколу Hashable.
7. Управляющие конструкции
7.1. Условные операторы
if / else if / else:
if temperature > 30 {
print("Hot")
} else if temperature > 10 {
print("Warm")
} else {
print("Cold")
}
switch:
Поддерживает сопоставление с шаблонами, диапазонами, кортежами, опционалами.
switch statusCode {
case 200:
print("OK")
case 400...499:
print("Client error")
case let x where x >= 500:
print("Server error: \(x)")
default:
print("Unknown")
}
Каждый case должен содержать хотя бы одно исполняемое выражение или использовать break.
7.2. Циклы
for-in:
for item in collection { ... }
for i in 0..<10 { ... }
for (index, value) in array.enumerated() { ... }
while:
while condition {
// выполняется, пока условие истинно
}
repeat-while:
repeat {
// тело цикла
} while condition
8. Функции
Функция — это блок кода с именем, параметрами и возвращаемым типом.
func greet(name: String) -> String {
return "Hello, \(name)!"
}
Вызов:
let message = greet(name: "Anna")
8.1. Параметры
- Именованные параметры:
func f(label name: Type) - Без метки:
_ name: Type - Значения по умолчанию:
name: String = "Guest" - Вариадические параметры:
numbers: Int... - In-out параметры:
inout value: Type(изменяют переданную переменную)
8.2. Возврат значений
Функция может возвращать:
- Одно значение
- Кортеж
- Ничего (
Voidили())
8.3. Вложенные функции
Функции могут быть определены внутри других функций.
func chooseFunction(isFast: Bool) -> (Int) -> Int {
func fast(x: Int) -> Int { return x * 2 }
func slow(x: Int) -> Int { return x + 10 }
return isFast ? fast : slow
}
Пользовательские типы и объектная модель
1. Структуры (struct)
Структура — это составной тип данных, объединяющий свойства и методы. Структуры в Swift являются значимыми типами (value types): при присваивании или передаче в функцию создаётся копия.
struct Point {
var x: Double
var y: Double
func distance(from other: Point) -> Double {
let dx = x - other.x
let dy = y - other.y
return (dx * dx + dy * dy).squareRoot()
}
mutating func move(by deltaX: Double, deltaY: Double) {
x += deltaX
y += deltaY
}
}
Особенности:
- Поддерживают свойства, методы, инициализаторы
- Не поддерживают наследование
- Методы, изменяющие состояние, помечаются как
mutating - Имеют автоматически сгенерированный инициализатор-член (
memberwise initializer), если не определён пользовательский
2. Классы (class)
Класс — это ссылочный тип (reference type), экземпляры которого разделяются по ссылке. Классы поддерживают наследование, переопределение методов и деструкторы.
class Vehicle {
var brand: String
var maxSpeed: Int
init(brand: String, maxSpeed: Int) {
self.brand = brand
self.maxSpeed = maxSpeed
}
func describe() -> String {
return "Vehicle: \(brand), max speed \(maxSpeed)"
}
deinit {
print("\(brand) deallocated")
}
}
Особенности:
- Поддерживают наследование
- Экземпляры сравниваются оператором
===(тождественность ссылок) - Имеют один или несколько инициализаторов
- Могут содержать деструктор (
deinit) - Не имеют автоматического memberwise initializer
3. Инициализация
Инициализатор — это метод, создающий новый экземпляр типа.
3.1. Инициализаторы в структурах
Если не указан явно, Swift генерирует инициализатор со всеми свойствами:
let p = Point(x: 1.0, y: 2.0)
Пользовательские инициализаторы могут вызывать другие инициализаторы через self.init(...).
3.2. Инициализаторы в классах
Классы требуют, чтобы все свойства были инициализированы до завершения инициализации.
Типы инициализаторов:
- Обозначенный (
designated) — основной инициализатор класса - Удобный (
convenience) — вторичный, должен вызывать обозначенный
Правила цепочки инициализации:
- Обозначенный инициализатор подкласса должен вызвать обозначенный инициализатор суперкласса
- Удобный инициализатор должен вызвать другой инициализатор в том же классе
- Удобный инициализатор подкласса может напрямую вызвать удобный инициализатор суперкласса, если цепочка ведёт к обозначенному
Пример:
class Car: Vehicle {
var numberOfDoors: Int
init(brand: String, maxSpeed: Int, doors: Int) {
self.numberOfDoors = doors
super.init(brand: brand, maxSpeed: maxSpeed)
}
convenience init(brand: String) {
self.init(brand: brand, maxSpeed: 200, doors: 4)
}
}
4. Наследование
Подкласс наследует свойства, методы и индексы от суперкласса.
Переопределение:
- Методы, свойства и индексы переопределяются с помощью
override - Для запрета переопределения используется
final
class ElectricCar: Car {
var batteryCapacity: Double
init(brand: String, battery: Double) {
self.batteryCapacity = battery
super.init(brand: brand, maxSpeed: 250, doors: 4)
}
override func describe() -> String {
return super.describe() + ", battery: \(batteryCapacity) kWh"
}
}
5. Перечисления (enum)
Перечисление — это тип, определяющий группу связанных значений.
enum CompassPoint {
case north, south, east, west
}
var direction = CompassPoint.north
direction = .south
5.1. Сырые значения (raw values)
Могут быть строками, символами или числовыми типами.
enum Planet: Int {
case mercury = 1, venus, earth, mars
}
let earth = Planet.earth
print(earth.rawValue) // 3
5.2. Ассоциированные значения (associated values)
Позволяют хранить дополнительные данные в каждом случае.
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
5.3. Методы в перечислениях
enum NetworkResponse {
case success(Data)
case failure(Error)
var isSuccess: Bool {
switch self {
case .success: return true
case .failure: return false
}
}
}
6. Протоколы (protocol)
Протокол определяет интерфейс — набор требований к свойствам, методам и индексам.
protocol Drawable {
var area: Double { get }
func render()
}
Тип соответствует протоколу, если реализует все его требования.
struct Circle: Drawable {
var radius: Double
var area: Double {
return .pi * radius * radius
}
func render() {
print("Drawing circle with radius \(radius)")
}
}
6.1. Требования к свойствам
{ get }— только для чтения{ get set }— для чтения и записи
6.2. Требования к методам
Могут быть экземплярными или статическими (static или class).
6.3. Протоколы как типы
Переменные могут иметь тип протокола:
let shapes: [Drawable] = [Circle(radius: 2), Square(side: 3)]
6.4. Расширения протоколов
Можно добавлять реализацию по умолчанию:
extension Drawable {
func logArea() {
print("Area: \(area)")
}
}
6.5. Композиция протоколов
Тип может соответствовать нескольким протоколам:
struct Player: Equatable, Codable, CustomStringConvertible {
let name: String
let score: Int
var description: String {
return "\(name): \(score)"
}
static func == (lhs: Player, rhs: Player) -> Bool {
return lhs.name == rhs.name && lhs.score == rhs.score
}
}
7. Расширения (extension)
Расширение добавляет новые функциональные возможности существующему типу — даже встроенному.
extension Int {
var squared: Int {
return self * self
}
func times(_ closure: () -> Void) {
for _ in 0..<self {
closure()
}
}
}
print(5.squared) // 25
3.times { print("Hello") }
Возможности расширений:
- Добавление вычисляемых свойств
- Определение новых методов
- Реализация протоколов
- Добавление инициализаторов (но не сохраняемых свойств)
8. Управление памятью: ARC
Swift использует Automatic Reference Counting (ARC) для управления памятью объектов классов.
- Каждый раз, когда создаётся новая сильная ссылка (
strong) на экземпляр, счётчик ссылок увеличивается - Когда счётчик достигает нуля, память освобождается
8.1. Сильные, слабые и несвязанные ссылки
strong— стандартная ссылка (по умолчанию)weak— не увеличивает счётчик ссылок; всегда опциональная; становитсяnil, когда объект уничтоженunowned— не увеличивает счётчик; не опциональная; предполагает, что объект живёт дольше
Используются для разрыва сильных циклических ссылок (retain cycles).
Пример с замыканием:
class NetworkManager {
var onComplete: (() -> Void)?
func fetchData() {
onComplete = { [weak self] in
guard let self = self else { return }
// безопасное использование self
}
}
}
9. Индексы (subscript)
Позволяют обращаться к элементам типа через квадратные скобки.
struct TimesTable {
let multiplier: Int
subscript(index: Int) -> Int {
return multiplier * index
}
}
let threeTimesTable = TimesTable(multiplier: 3)
print(threeTimesTable[6]) // 18
Индексы могут иметь несколько параметров и возвращать любые типы.
Замыкания, обобщения, ошибки, асинхронность
1. Замыкания (Closures)
Замыкание — это самодостаточный блок функциональности, который может быть передан и использован в коде. Функции в Swift являются частным случаем замыканий.
1.1. Синтаксис замыканий
Полная форма:
{ (parameters) -> ReturnType in
statements
}
Пример:
let add = { (a: Int, b: Int) -> Int in
return a + b
}
print(add(2, 3)) // 5
1.2. Упрощённые формы
-
Вывод типов позволяет опустить аннотации:
let multiply = { (x, y) in x * y } -
Однострочные замыкания не требуют
return:let square = { $0 * $0 } -
Автоматические имена аргументов:
$0,$1,$2...
1.3. Типы замыканий
Замыкание имеет тип (ParameterTypes) -> ReturnType.
Примеры:
let handler: () -> Void = { print("Done") }
let transformer: (String) -> Int = { $0.count }
1.4. Escape-замыкания
По умолчанию замыкания не escaping — они вызываются до завершения функции. Если замыкание сохраняется для вызова позже (например, в свойстве или асинхронной операции), оно помечается как @escaping.
var completionHandlers: [() -> Void] = []
func addHandler(_ handler: @escaping () -> Void) {
completionHandlers.append(handler)
}
1.5. Autoclosure
@autoclosure автоматически оборачивает выражение в замыкание. Используется для отложенного вычисления.
func assert(_ condition: @autoclosure () -> Bool, _ message: String) {
if !condition() {
print("Assertion failed: \(message)")
}
}
assert(2 + 2 == 5, "Math is broken")
2. Обобщённое программирование (Generics)
Обобщения позволяют писать гибкий, многоразовый код, не привязанный к конкретным типам.
2.1. Обобщённые функции
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var x = 5, y = 10
swapValues(&x, &y)
<T> — параметр типа. Может быть несколько: <T, U, V>.
2.2. Обобщённые типы
struct Stack<Element> {
private var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element? {
return items.popLast()
}
}
var stringStack = Stack<String>()
stringStack.push("Hello")
2.3. Ограничения типов (where)
Можно накладывать требования на обобщённые типы:
func isEqual<T: Equatable>(_ a: T, _ b: T) -> Bool {
return a == b
}
// Или через where:
func merge<T>(_ array1: [T], _ array2: [T]) -> [T] where T: Comparable {
return (array1 + array2).sorted()
}
Распространённые ограничения:
Equatable— поддержка==Comparable— поддержка<,>Hashable— возможность использования вSetиDictionaryCodable— поддержка сериализации
2.4. Обобщённые расширения
extension Array where Element: Numeric {
var sum: Element {
return reduce(0, +)
}
}
let numbers = [1, 2, 3, 4]
print(numbers.sum) // 10
3. Управление ошибками
Swift использует систему бросания и перехвата ошибок на основе протокола Error.
3.1. Определение ошибок
enum NetworkError: Error {
case invalidURL
case noData
case decodingFailed
}
3.2. Функции, которые могут выбрасывать ошибки
Помечаются как throws:
func fetchData(from url: String) throws -> Data {
guard let validURL = URL(string: url) else {
throw NetworkError.invalidURL
}
// ... имитация загрузки
throw NetworkError.noData
}
3.3. Обработка ошибок
-
do-catch:do {
let data = try fetchData(from: "https://example.com")
} catch NetworkError.invalidURL {
print("Bad URL")
} catch {
print("Unexpected error: \(error)")
} -
try?— преобразует ошибку в опционал:let data = try? fetchData(from: "...")
// data: Data? -
try!— подавляет ошибку (опасно):let data = try! fetchData(from: "...") // аварийное завершение при ошибке
3.4. Передача ошибок
Функция, вызывающая throws-функцию, также должна быть throws или обрабатывать ошибку.
4. Асинхронное программирование (async/await)
Начиная с Swift 5.5, язык поддерживает нативную асинхронность.
4.1. Асинхронные функции
Помечаются как async:
func fetchUser(id: Int) async throws -> User {
// имитация сетевого запроса
return User(id: id, name: "User \(id)")
}
4.2. Вызов асинхронных функций
Используется await:
Task {
do {
let user = try await fetchUser(id: 42)
print(user.name)
} catch {
print("Failed to load user")
}
}
await приостанавливает выполнение текущей задачи, не блокируя поток.
4.3. Параллельное выполнение
-
Последовательно:
let user1 = try await fetchUser(id: 1)
let user2 = try await fetchUser(id: 2) -
Параллельно:
async let u1 = fetchUser(id: 1)
async let u2 = fetchUser(id: 2)
let (user1, user2) = try await (u1, u2)
4.4. Задачи (Task)
Task — это единица асинхронной работы.
let task = Task {
await someAsyncWork()
}
// task.cancel() — отмена
5. Акторы (Actor)
Актор — это ссылочный тип, обеспечивающий безопасность данных в многопоточной среде. Каждый актор имеет собственную очередь выполнения.
actor BankAccount {
private var balance: Double = 0
init(initialBalance: Double) {
balance = initialBalance
}
func deposit(_ amount: Double) {
balance += amount
}
func withdraw(_ amount: Double) throws {
guard amount <= balance else {
throw BankError.insufficientFunds
}
balance -= amount
}
nonisolated func description() -> String {
return "BankAccount"
}
}
Особенности:
- Все mutable свойства и методы изолированы внутри актора
- Доступ к ним из внешнего кода требует
await nonisolated— методы, не обращающиеся к изолированным данным, могут вызываться безawait
Использование:
let account = BankActor(initialBalance: 100)
await account.deposit(50)
Акторы предотвращают гонки данных (data races).
6. Работа с памятью: продвинутые аспекты
6.1. Сильные циклы (retain cycles)
Возникают, когда два объекта удерживают друг друга через сильные ссылки.
Решение:
- Использовать
weakилиunownedв замыканиях и делегатах - Пример делегата:
protocol DataReceiver: AnyObject {
func receive(data: Data)
}
class DataManager {
weak var receiver: DataReceiver?
}
6.2. Weak vs Unowned
weak: используется, когда ссылка может статьnil(например, делегат)unowned: используется, когда гарантируется, что объект живёт дольше (например, закрытие в контроллере)
Неправильное использование unowned приводит к аварийному завершению.
6.3. Утечки памяти в замыканиях
class ViewController {
var onClose: (() -> Void)?
func setup() {
onClose = { [weak self] in
self?.dismiss()
}
}
}
Без [weak self] замыкание удерживает self, создавая цикл.
Стандартная библиотека и повседневные инструменты
1. Коллекции: углублённое использование
1.1. Массивы (Array)
Инициализация:
let empty = [Int]()
let repeated = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0]
let fromRange = Array(1...5) // [1, 2, 3, 4, 5]
Основные операции:
append(_:),append(contentsOf:)insert(_:at:),remove(at:),removeLast()first,last— опциональные значенияisEmpty,count
Изменение порядка:
reverse(),reversed()— изменяет или возвращает копиюsort(),sorted(by:)— сортировка на месте или с возвратом
Поиск:
first(where:),last(where:)contains(where:)index(of:)— устаревший; вместо негоfirstIndex(where:)
Преобразования:
map(transform:)— преобразует каждый элементcompactMap(transform:)— фильтруетnilпри преобразовании опционаловflatMap(transform:)— применяетmap, затем «выравнивает» вложенные коллекции
Фильтрация и разбиение:
filter(includeElement:)partition(by:)— перемещает элементы, удовлетворяющие условию, в конецsplit(separator:maxSplits:omittingEmptySubsequences:)— для массивов символов
Свёртка:
reduce(initialResult:_:)— накапливает значениеreduce(into:_:)— эффективнее для мутабельных значений (например, словарей)
Пример:
let words = ["apple", "banana", "cherry"]
let lengths = words.map { $0.count } // [5, 6, 6]
let total = lengths.reduce(0, +) // 17
1.2. Словари (Dictionary)
Инициализация:
let dict: [String: Int] = [:]
let fromPairs = Dictionary(uniqueKeysWithValues: [("a", 1), ("b", 2)])
Обновление:
dict[key] = value— добавление или обновлениеdict.updateValue(_:forKey:)— возвращает старое значение какOptional
Удаление:
dict[key] = nildict.removeValue(forKey:)— возвращает удалённое значение
Итерация:
for (key, value) in dict {
print("\(key): \(value)")
}
Группировка:
let grouped = words.grouped(by: { $0.first! })
// ["a": ["apple"], "b": ["banana"], "c": ["cherry"]]
Объединение:
let merged = dict1.merging(dict2) { current, _ in current }
// сохраняет значение из dict1 при конфликте
1.3. Множества (Set)
Операции над множествами:
union(_:)— объединениеintersection(_:)— пересечениеsymmetricDifference(_:)— симметрическая разностьsubtracting(_:)— разность
Проверки:
isSubset(of:),isSuperset(of:)isDisjoint(with:)— не имеют общих элементов
Пример:
let primes: Set = [2, 3, 5, 7]
let odds: Set = [1, 3, 5, 7, 9]
let common = primes.intersection(odds) // [3, 5, 7]
2. Строки и символы
2.1. Индексы и подстроки
Строки в Swift не поддерживают целочисленную индексацию из-за Unicode.
let greeting = "Hello"
let index = greeting.index(greeting.startIndex, offsetBy: 1)
let char = greeting[index] // "e"
let substring = greeting[greeting.startIndex..<index] // "H"
Полезные свойства:
startIndex,endIndexindices— диапазон всех допустимых индексов
2.2. Поиск и замена
contains("text")hasPrefix("He"),hasSuffix("lo")replacingOccurrences(of:with:)
2.3. Разделение
let components = "a,b,c".split(separator: ",") // [Substring]
let strings = components.map(String.init)
2.4. Регулярные выражения (Swift 5.7+)
import RegexBuilder
let regex = Regex {
OneOrMore(.digit)
"."
OneOrMore(.digit)
}
if let match = "Version 1.2.3".firstMatch(of: regex) {
print(match.0) // "1.2"
}
Или через литералы (Swift 5.7+):
if let match = "123-456" firstMatch /(\d+)-(\d+)/ {
print(match.1, match.2) // "123", "456"
}
3. Кодирование и сериализация
3.1. Codable
Протокол Codable объединяет Encodable и Decodable.
struct User: Codable {
var name: String
var age: Int
}
Кодирование в JSON:
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(user)
let json = String(data: data, encoding: .utf8)
Декодирование:
let decoder = JSONDecoder()
let user = try decoder.decode(User.self, from: jsonData)
3.2. Настройка ключей
struct Response: Codable {
var userID: Int
var userName: String
enum CodingKeys: String, CodingKey {
case userID = "id"
case userName = "name"
}
}
3.3. Вложенные структуры
Поддерживается автоматически:
{
"user": { "name": "Alice" }
}
struct Wrapper: Codable {
var user: User
}
4. Работа с файлами и путями (через Foundation)
Хотя Swift сам по себе не содержит файловой системы, в связке с Foundation доступны мощные инструменты.
4.1. URL — основной тип для путей
let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileURL = documents.appendingPathComponent("data.json")
4.2. Чтение и запись
// Запись
try jsonData.write(to: fileURL)
// Чтение
let loadedData = try Data(contentsOf: fileURL)
4.3. Проверка существования
if FileManager.default.fileExists(atPath: fileURL.path) {
// файл существует
}
5. Локализация и интернационализация
5.1. LocalizedStringKey
В SwiftUI используется автоматическая локализация через Text("Hello").
В обычном Swift:
let message = NSLocalizedString("greeting", comment: "Welcome message")
Файл Localizable.strings:
"greeting" = "Здравствуйте!";
5.2. Форматирование дат и чисел
Через Foundation:
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
let formatted = formatter.string(from: 12345 as NSNumber) // "12,345"
6. Вспомогательные утилиты
6.1. Таймеры и задержки
Через DispatchQueue:
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
print("Delayed execution")
}
6.2. Генерация случайных значений
Int.random(in: 1...100)
Bool.random()
Double.random(in: 0..<1)
Расширение для массива:
extension Collection {
func randomElement() -> Element? {
guard !isEmpty else { return nil }
let index = Int.random(in: startIndex..<endIndex)
return self[index]
}
}
6.3. Кэширование и ленивые вычисления
-
lazy— откладывает вычисления до момента использования:let squares = (1...1000).lazy.map { $0 * $0 }.prefix(5) -
LazySequenceиLazyCollection— экономят память и время
7. Паттерны проектирования в Swift
7.1. Singleton
class NetworkManager {
static let shared = NetworkManager()
private init() {}
}
7.2. Builder
Используется для сложных инициализаций:
struct URLRequestBuilder {
var url: URL?
var method: String = "GET"
var headers: [String: String] = [:]
func setURL(_ url: URL) -> Self {
var copy = self
copy.url = url
return copy
}
func build() throws -> URLRequest {
guard let url = url else { throw URLError(.badURL) }
var request = URLRequest(url: url)
request.httpMethod = method
request.allHTTPHeaderFields = headers
return request
}
}
7.3. Observer через замыкания
class ValueHolder<T> {
private var _value: T
private var observers: [(T) -> Void] = []
init(_ value: T) {
self._value = value
}
var value: T {
didSet {
observers.forEach { $0(value) }
}
}
func observe(_ handler: @escaping (T) -> Void) {
observers.append(handler)
}
}
Инструменты, практики и экосистема
1. Модульность и организация кода
1.1. Модули
Каждый фреймворк или исполняемый файл в Swift представляет собой модуль. Модуль — это единое пространство имён, содержащее типы, функции и расширения.
- Имя модуля совпадает с именем цели (target) в Xcode
- Доступ к публичным элементам другого модуля осуществляется через
import
import Foundation
import UIKit
1.2. Уровни доступа
Swift предоставляет пять уровней доступа:
| Уровень | Описание |
|---|---|
open | Доступен из любого модуля; может быть подклассом и переопределён вне модуля |
public | Доступен из любого модуля; не может быть подклассом/переопределён вне модуля |
internal | Доступен только внутри модуля (по умолчанию) |
fileprivate | Доступен только в пределах одного файла |
private | Доступен только в пределах ближайшей области видимости (например, структуры) |
Пример:
public class APIManager {
private let session = URLSession.shared
internal func fetchData() { ... }
}
1.3. Разделение кода на файлы
Рекомендуется:
- Один основной тип на файл (класс, структура, перечисление)
- Вспомогательные типы (например,
CodingKeys) могут находиться в том же файле - Расширения, реализующие протоколы, выносятся в отдельные файлы:
User+Codable.swift,Array+Utilities.swift
2. Тестирование
2.1. Unit-тесты (XCTest)
Создаются в отдельной цели тестирования. Каждый тест — метод, начинающийся с test.
import XCTest
@testable import MyApp
class UserManagerTests: XCTestCase {
func testUserCreation() {
let user = User(name: "Alice", age: 30)
XCTAssertEqual(user.name, "Alice")
XCTAssertTrue(user.isActive)
}
func testInvalidAgeThrowsError() throws {
XCTAssertThrowsError(try User(name: "Bob", age: -5)) { error in
XCTAssertEqual(error as? UserError, .invalidAge)
}
}
}
@testable importпозволяет получать доступ кinternalэлементам- Используются утверждения:
XCTAssertEqual,XCTAssertTrue,XCTAssertThrowsErrorи др.
2.2. Асинхронные тесты
func testAsyncFetch() async throws {
let manager = NetworkManager()
let user = try await manager.fetchUser(id: 42)
XCTAssertEqual(user.id, 42)
}
Тест автоматически ожидает завершения асинхронной операции.
2.3. Mock-объекты
Для изоляции зависимостей создаются заглушки:
protocol NetworkService {
func fetchUser(id: Int) async throws -> User
}
class MockNetworkService: NetworkService {
var shouldFail = false
func fetchUser(id: Int) async throws -> User {
if shouldFail { throw NetworkError.timeout }
return User(id: id, name: "Mock")
}
}
3. Документирование кода
Swift поддерживает генерацию документации через комментарии.
3.1. Синтаксис
/// Загружает пользователя по идентификатору.
///
/// Эта функция выполняет сетевой запрос к API и возвращает объект `User`.
/// При ошибке сети или недопустимом ответе выбрасывается `NetworkError`.
///
/// - Parameters:
/// - id: Уникальный идентификатор пользователя. Должен быть положительным.
/// - Returns: Объект `User`, содержащий данные профиля.
/// - Throws: `NetworkError.invalidID`, если `id <= 0`.
///
/// - Important: Вызов этой функции требует активного интернет-соединения.
public func fetchUser(id: Int) throws -> User {
// ...
}
Поддерживаемые ключевые слова:
- Parameters:- Returns:- Throws:- Important:,- Note:,- Warning:- SeeAlso:,- Since:,- Version:
3.2. Генерация документации
Инструменты:
- Xcode Quick Help — отображает документацию при наведении
- SourceDocs — генерирует HTML-документацию
- Jazzy — популярный генератор документации в стиле Apple
4. Инструменты разработки
4.1. Swift Package Manager (SPM)
Встроенный менеджер зависимостей и сборки.
Файл Package.swift:
let package = Package(
name: "MyLibrary",
platforms: [.iOS(.v15)],
products: [
.library(name: "MyLibrary", targets: ["MyLibrary"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-algorithms", from: "1.0.0")
],
targets: [
.target(
name: "MyLibrary",
dependencies: ["Algorithms"]
)
]
)
4.2. Linter и форматирование
- SwiftLint — проверяет стиль кода и потенциальные ошибки
- SwiftFormat — автоматически форматирует код
Пример правила .swiftlint.yml:
disabled_rules:
- force_cast
- line_length
opt_in_rules:
- empty_count
4.3. Отладка
print()— базовый вывод#file,#line,#function— макросы для логирования:func log(_ message: String, file: String = #file, line: Int = #line) {
print("[\(file.components(separatedBy: "/").last!):\(line)] \(message)")
}os_log— производительный системный логгер (черезOSLog)
5. Производительность и оптимизация
5.1. Выбор между struct и class
- Используйте
structпо умолчанию — они быстрее, безопаснее и не создают retain cycles - Используйте
class, когда требуется:- Наследование
- Ссылочная семантика (разделяемое состояние)
- Делегирование с
weak
5.2. Избегание копирования больших структур
Если структура содержит много данных, передача по значению может быть дорогой. В таких случаях:
- Используйте
inoutдля мутации - Рассмотрите
classилиactor - Используйте
Copy-on-Write(CoW) вручную черезisKnownUniquelyReferenced
5.3. Lazy properties
Откладывают инициализацию до первого обращения:
lazy var expensiveObject = computeExpensiveValue()
5.4. Использование final
Пометка класса как final позволяет компилятору оптимизировать вызовы методов:
final class Utility {
func helper() { ... }
}
5.5. Измерение производительности
func measure<T>(_ block: () throws -> T) rethrows -> T {
let start = CFAbsoluteTimeGetCurrent()
let result = try block()
let time = CFAbsoluteTimeGetCurrent() - start
print("Execution time: \(time)s")
return result
}
6. Безопасность кода
6.1. Избегание принудительного развёртывания
Плохо:
let value = optional!
Хорошо:
if let value = optional {
// безопасное использование
}
// или
guard let value = optional else { return }
6.2. Проверка границ
Используйте безопасные расширения:
extension Collection {
subscript(safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
6.3. Обработка ошибок вместо fatalError
Избегайте аварийного завершения в production-коде. Используйте Result, Optional или выбрасывание ошибок.
7. Лучшие практики стиля и читаемости
7.1. Именование
- Типы (
struct,class,enum,protocol) —UpperCamelCase - Переменные, функции, свойства —
lowerCamelCase - Константы —
lowerCamelCase(неUPPER_CASE) - Булевы свойства — префиксы
is,has,can:isEnabled,hasData
7.2. Длина строки
Рекомендуется ограничивать строку 100–120 символами.
7.3. Комментарии
- Комментарии объясняют «почему», а не «что»
- Самодокументируемый код предпочтительнее комментариев
7.4. Функции
- Короткие, одноцелевые функции
- Минимум побочных эффектов
- Чистые функции (без состояния) предпочтительны
7.5. Расширения
Группируйте логически связанный код:
// MARK: - Codable
extension User: Codable { ... }
// MARK: - Equatable
extension User: Equatable { ... }
8. Экосистема и совместимость
8.1. Версии Swift
- Swift стремится к обратной совместимости
- Новые возможности помечаются как
@available(iOS 15.0, *) - Используйте условную компиляцию для поддержки старых версий:
if #available(iOS 15.0, *) {
await someAsyncFunction()
} else {
// fallback
}
8.2. Кросс-платформенность
Swift работает на:
- Apple-платформах (iOS, macOS, watchOS, tvOS)
- Linux (через Swift.org)
- Windows (экспериментально)
Код, не зависящий от Foundation или UIKit, может быть переносимым.
8.3. Интеграция с Objective-C
- Классы Swift, наследующие
NSObject, могут быть доступны в Objective-C - Используйте
@objcи@objcMembersдля экспорта - Не все возможности Swift доступны в Objective-C (например, generics, кортежи)